package net

import (
	"MsSgServer/utils"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/forgoer/openssl"
	"github.com/gorilla/websocket"
	"github.com/mitchellh/mapstructure"
	"log"
	"sync"
	"time"
)

// 对应的超时设置
type syncCtx struct {
	// Goroutine 的上下文，包含 Goroutine 的运行状态、环境、现场等信息
	ctx     context.Context
	cancel  context.CancelFunc
	outChan chan *RspBody
}

func NewSyncCtx() *syncCtx {
	cxt, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	return &syncCtx{
		ctx:     cxt,
		cancel:  cancel,
		outChan: make(chan *RspBody),
	}
}
func (s *syncCtx) wait() *RspBody {
	select {
	case msg := <-s.outChan:
		return msg
	case <-s.ctx.Done():
		// 超时
		log.Println("代理服务响应消息超时！")
		return nil

	}
}

// ClientConn 客户端链接
type ClientConn struct {
	wsConn        *websocket.Conn
	isClosed      bool                   // 监听当前客户端是否是关闭状态
	property      map[string]interface{} // 设置一些客户端属性
	propertyLock  sync.RWMutex           // 属性读写锁
	Seq           int64                  // 序列号
	handshake     bool                   // 握手
	handshakeChan chan bool
	onPush        func(conn *ClientConn, body *RspBody) // 给 websocket代理服务端 发送消息
	onClose       func(conn *ClientConn)                // 给 websocket代理服务端 发送关闭消息
	syncCtxMap    map[int64]*syncCtx                    // 客户端链接 -》 对应数据
	syncCtxLock   sync.RWMutex                          // 客户端链接 -》 对应数据 读写锁
}

// NewClientConn 创建新的客户端链接
func NewClientConn(wsConn *websocket.Conn) *ClientConn {
	return &ClientConn{
		wsConn:        wsConn,
		handshakeChan: make(chan bool),
	}
}

// Start 网关服务 链接Start
func (c *ClientConn) Start() bool {
	// 做的事情是一直不停的接收消息

	// 等待握手的消息返回
	c.handshake = false
	// 读消息
	go c.wsReadLoop()
	return c.waitHandShake()
}

func (c *ClientConn) waitHandShake() bool {
	// 思考：万一 程序出现问题 超时了 一直响应不到 一般来说需要设置超时时间 如何设置呢？
	ctx, cancle := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancle()
	// 等待握手成功 通过 handshakeChan 来判断
	select {
	case _ = <-c.handshakeChan:
		log.Println("握手成功！")
		return true
	case <-ctx.Done():
		// 超时设置
		log.Println("握手超时！")
		return false
	}
}

func (c *ClientConn) wsReadLoop() {
	//for {
	//	// 需要一直不停的读消息
	//	_, data, err := c.wsConn.ReadMessage()
	//	fmt.Println("data: ", data)
	//	fmt.Println("err: ", err)
	//	// 读消息 可能会收到很多消息 握手 心跳 请求 信息（account.login）
	//	// 服务端写消息
	//	// 如果收到握手消息了
	//	c.handshake = true
	//	c.handshakeChan <- true
	//}
	// 先读到客户端发送过来的消息 然后进行处理 再 回消息
	// 经过路由 实际处理程序
	defer func() {
		if err := recover(); err != nil {
			log.Println("捕捉到异常:", err)
			c.Close()
		}
	}()
	for {
		// 读取发送过来的消息
		_, data, err := c.wsConn.ReadMessage()
		if err != nil {
			log.Println("收消息出现错误: ", err)
		}
		fmt.Println(data)
		// 收到消息后进行解析 前端发送过来的消息格式为 json格式
		// 1. data 解压 unzip
		data, err = utils.UnZip(data)
		if err != nil {
			log.Println("解压数据出错， 非法格式: ", err)
			// 进入下一循环
			continue
		}
		// 2. 前端消息时加密的 需要进行解密 从属性里面 拿到 加密的 secretKey
		secretKey, err := c.GetProperty("secretKey")
		// secretKey 有加密 进行解密
		if err == nil {
			// 有加密
			key := secretKey.(string)
			// 客户端传过来的数据是加密的 需要解密
			d, err := utils.AesCBCDecrypt(data, []byte(key), []byte(key), openssl.ZEROS_PADDING)
			if err != nil {
				log.Println("数据格式有误，解密失败: ", err)
			} else {
				// 解密完成 data 重新赋值
				data = d
			}
		}
		// 3. data 转为 请求体body
		body := &RspBody{}
		// 将链接获得的 data（json） 数据 转换成 请求体所需的格式（请求体）
		err = json.Unmarshal(data, body)
		if err != nil {
			log.Println("数据格式有误，非法格式: ", err)
		} else {
			// 判断是握手还是一些别的请求
			if body.Seq == 0 {
				if body.Name == HandshakeMsg {
					// 是握手消息
					// 获取秘钥
					hs := &Handshake{}
					mapstructure.Decode(body.Msg, hs)
					if hs.Key != "" {
						c.SetProperty("secretKey", hs.Key)
					} else {
						c.RemoveProperty("secretKey")
					}
					c.handshake = true
					c.handshakeChan <- true
				} else {
					// 不是握手消息
					if c.onPush != nil {
						c.onPush(c, body)
					}
				}
			} else {
				c.syncCtxLock.RLock()
				ctx, ok := c.syncCtxMap[body.Seq]
				c.syncCtxLock.RUnlock()
				if ok {
					ctx.outChan <- body
				} else {
					log.Println("no seq syncCtx find")
				}
			}

		}

	}
	// 程序循环出现问题 调用关闭
	c.Close()
}

func (c *ClientConn) Close() {
	_ = c.wsConn.Close()
}

// SetProperty 设置属性
func (c *ClientConn) SetProperty(key string, value interface{}) {
	// 首先进行加锁
	c.propertyLock.Lock()
	// 结束进行解锁
	defer c.propertyLock.Unlock()
	// 修改属性
	c.property[key] = value
}

// GetProperty 获取属性
func (c *ClientConn) GetProperty(key string) (interface{}, error) {
	// 采用读锁
	c.propertyLock.RLock()
	defer c.propertyLock.RUnlock()
	// 判断一下是否可以拿到	 返回读取信息
	if value, ok := c.property[key]; ok {
		return value, nil
	} else {
		return nil, errors.New("no property found")
	}

}

// RemoveProperty 删除属性
func (c *ClientConn) RemoveProperty(key string) {
	// 写锁
	c.propertyLock.Lock()
	defer c.propertyLock.Unlock()
	// 原生 delete方法
	delete(c.property, key)
}

// Addr 返回地址
func (c *ClientConn) Addr() string {
	// 返回远程地址
	return c.wsConn.RemoteAddr().String()
}

// Push 加密写
func (c *ClientConn) Push(name string, data interface{}) {
	// 将回复的消息进行封装
	rsp := &WsMsgRsp{Body: &RspBody{Name: name, Msg: data, Seq: 0}}
	// 封装的消息传入返回通道
	c.write(rsp.Body)
}

func (c *ClientConn) write(body interface{}) {
	// 将 处理完成的响应体 转成 json
	data, err := json.Marshal(body)
	if err != nil {
		log.Println(err)
	}
	// 2. 前端消息时加密的 需要进行解密 从属性里面 拿到 加密的 secretKey
	secretKey, err := c.GetProperty("secretKey")
	// secretKey 有加密 进行解密
	if err == nil {
		// 有加密
		key := secretKey.(string)
		// 数据做加密
		data, _ = utils.AesCBCEncrypt(data, []byte(key), []byte(key), openssl.ZEROS_PADDING)
	}
	// 加密完成之后进行压缩
	if data, err := utils.Zip(data); err == nil {
		// 压缩完成之后 进行写数据
		c.wsConn.WriteMessage(websocket.BinaryMessage, data)
	}
}
